﻿// Copyright (c) 2012 Francis Xavier Joseph Pulikotil
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

using System;
using System.Diagnostics;
using System.Text;

namespace Daabli
{
    public class BoolReader : IObjectReader
    {
        public System.Type Type { get { return typeof(bool); } }
        public Grouping Grouping { get { return Grouping.None; } }

        public object Read(object /*unused*/ obj, Reader r)
        {
            string token = string.Empty;
            if (!r.ReadToken(ref token))
            {
                r.Log.Write(MessageType.Error, "'{0}' value expected", Type.ToString());
                return null;
            }

            var value = token.ToUpperInvariant();

            if (string.CompareOrdinal(value, "TRUE") == 0)
                return true;
            if (string.CompareOrdinal(value, "FALSE") == 0)
                return false;

            r.Log.Write(MessageType.Error, "invalid '{0}' value: {1}", Type.ToString(), token);
            return null;
        }
    }

    // Note: Used to provide object readers for the following types -
    // byte, sbyte, decimal, double, float, int, uint, long, ulong, short, ushort
    public class ValueReader<T> : IObjectReader
    {
        // Types
        public delegate bool TryParseDelegate(string input, out T value);

        // Members
        private TryParseDelegate _tryParseFunc;

        // IObjectReader properties
        public System.Type Type { get { return typeof(T); } }
        public Grouping Grouping { get { return Grouping.None; } }

        // Constructor
        public ValueReader(TryParseDelegate tryParseFunc)
        {
            Debug.Assert(tryParseFunc != null);
            _tryParseFunc = tryParseFunc;
        }

        // IObjectReader functions
        public object Read(object /*unused*/ obj, Reader r)
        {
            string token = string.Empty;
            if (!r.ReadToken(ref token))
            {
                r.Log.Write(MessageType.Error, "'{0}' value expected", Type.ToString());
                return null;
            }

            T value;
            if (!_tryParseFunc(token, out value))
            {
                r.Log.Write(MessageType.Error, "invalid '{0}' value: {1}", Type.ToString(), token);
                return null;
            }

            return value;
        }
    }

    public class CharReader : IObjectReader
    {
        public Type Type { get { return typeof(char); } }
        public Grouping Grouping { get { return Daabli.Grouping.None; } }

        public object Read(object /*unused*/ obj, Reader r)
        {
            // Characters are expected to be in numeric format, or literals enclosed in single quotes
            char result = default(char);

            if (r.PeekExpectedToken('\''))
            {
                if (!r.ReadExpectedToken('\''))
                {
                    r.Log.Write(MessageType.Error, "char values must be enclosed in single quotes");
                    return null;
                }

                if (!r.ReadLiteral(ref result))
                {
                    r.Log.Write(MessageType.Error, "char value expected");
                    return null;
                }

                if (!r.ReadExpectedLiteral('\''))
                {
                    r.Log.Write(MessageType.Error, "char values must be enclosed in single quotes");
                    return null;
                }
            }
            else // numeric format
            {
                string str = string.Empty;
                if (!r.ReadToken(ref str))
                {
                    r.Log.Write(MessageType.Error, "char value expected");
                    return null;
                }

                int value;
                if (!int.TryParse(str, out value) || (value < char.MinValue) || (value > char.MaxValue))
                {
                    r.Log.Write(MessageType.Error, "invalid char value: {0}", str);
                    return null;
                }

                result = (char)value;
            }

            return result;
        }
    }

    public class StringReader : IObjectReader
    {
        public System.Type Type { get { return typeof(string); } }
        public Grouping Grouping { get { return Grouping.None; } }

        public object Read(object /*unused*/ obj, Reader r)
        {
            if (!r.ReadExpectedToken('"'))
            {
                r.Log.Write(MessageType.Error, "string values must be enclosed in double quotes");
                return null;
            }

            StringBuilder strBuilder = new StringBuilder();
            while (true)
            {
                char ch = default(char);
                if (!r.ReadLiteral(ref ch))
                {
                    r.Log.Write(MessageType.Error, "string values must be enclosed in double quotes");
                    return null;
                }

                if (ch == '"')
                    break;

                if (ch == '\\')
                {
                    char ch2 = default(char);
                    if (!r.ReadLiteral(ref ch2))
                    {
                        r.Log.Write(MessageType.Error, "string values must be enclosed in double quotes");
                        return null;
                    }

                    switch (ch2)
                    {
                        case '"':
                        case '\\':
                            strBuilder.Append(ch2);
                            break;

                        case 'n':
                            strBuilder.Append('\r');
                            strBuilder.Append('\n');
                            break;

                        default:
                            r.Log.Write(MessageType.Error, "unrecognized character escape sequence: \\{0}", ch2);
                            return null;
                    }
                }
                else
                    strBuilder.Append(ch);
            }

            return strBuilder.ToString();
        }
    }

    public class EnumReader<T> : ValueReader<T>
        // Try to accept only enum types
        where T : struct, IComparable, IFormattable
    {
        private static bool TryParseEnum(string input, out T value)
        {
            try
            {
                value = (T)Enum.Parse(typeof(T), input, false);
            }
            catch (Exception)
            {
                value = default(T);
                return false;
            }

            return true;
        }

        public EnumReader()
            : base(TryParseEnum)
        {
            // Runtime check to make sure we're only working with enums. 
            Debug.Assert(typeof(T).IsSubclassOf(typeof(Enum)));
        }
    }
}
